﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace STSdb.MTL
{
    /// <summary>
    /// Used for Statistical and notification purposes of ReadLocators functionality
    /// </summary>
    public class ReadLocatorsHandler
    {
        TransactionsCoordinator _transactionCoordinator = null;

        public ReadLocatorsHandler(TransactionsCoordinator transactionCoordinator)
        {
            _transactionCoordinator = transactionCoordinator;
        }

        /// <summary>
        /// TableReadLocatorActivity
        /// </summary>
        private class TRLA
        {           
            /// <summary>
            /// Acquired Read Lock as:
            /// Value holds DateTime of ReadLock acquiring
            /// Key holds transactionThreadId
            /// </summary>
            Dictionary<int, long> aL = new Dictionary<int, long>();

            /// <summary>
            /// Lock for acquired locks
            /// </summary>
            ReaderWriterLockSlim _s = new ReaderWriterLockSlim();
            
            /// <summary>
            /// Adds read locator start activity
            /// </summary>
            /// <param name="transactionThreadId"></param>
            public void Add(int transactionThreadId)
            {
                long dtAdded=DateTime.Now.Ticks;

                _s.EnterWriteLock();
                try
                {

                    //Adding in case if key is not found, possible many read locators from one thread, we leave only the first occurance
                    if (!aL.ContainsKey(transactionThreadId))
                        aL.Add(transactionThreadId, dtAdded);
                }
                catch (System.Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    _s.ExitWriteLock();
                }

            }

            /// <summary>
            /// If table doesn't contain reading threads, returns true
            /// </summary>
            /// <returns></returns>
            public bool ContainsThreads()
            {
                _s.EnterReadLock();
                try
                {
                    return (aL.Count() > 0);
                }
                catch (System.Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    _s.ExitReadLock();
                }         
            }

            /// <summary>
            /// Removes locator activity.
            /// </summary>
            /// <param name="transactionThreadId"></param>
            public bool Remove(int transactionThreadId)
            {
                bool ReadLocatorWasFound = false;

                _s.EnterWriteLock();
                try
                {
                    //If such not found - abnormal behaviour, but, probably, possible in different software try catch situations
                    if (aL.ContainsKey(transactionThreadId))
                    {
                        ReadLocatorWasFound = true;
                        aL.Remove(transactionThreadId);
                    }
                }
                catch (System.Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    _s.ExitWriteLock();
                }

                return ReadLocatorWasFound;
            }        

            /// <summary>
            /// System Answer, we want to fins out if there are open READ LOCATORS till specified datetime
            /// </summary>
            /// <param name="dt"></param>
            /// <returns></returns>
            public bool IfThereAreOpenReadLocatorsTillSpecifiedDateTime(DateTime dt)
            {
                _s.EnterReadLock();
                try
                {
                    long searchDt = dt.Ticks;
                                       

                    foreach (var alkp in aL)
                    {
                        if (alkp.Value <= searchDt)
                            return true;
                    }

                    return false;
                }
                catch (System.Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    _s.ExitReadLock();
                }              

            }
        }

        /// <summary>
        /// Holds all active ReadLocators per table name and then per thread.
        /// This dictionary only grows, with adding new tables, nothing is deleted till the instance end of life cycle.
        /// </summary>
        Dictionary<string, TRLA> tablesReadLocks = new Dictionary<string, TRLA>();
        ReaderWriterLockSlim _sync_tablesReadLocks = new ReaderWriterLockSlim();
        /// <summary>
        /// Holds info about which threads read which tables, synchronized via _sync_tablesReadLocks
        /// </summary>
        Dictionary<int, List<string>> threadReadTables = new Dictionary<int, List<string>>();

        /// <summary>
        /// When some kind of read locator starts this function is called
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="transactionThreadId"></param>
        public void ReadLocatorStarted(string tableName, int transactionThreadId)
        {
            //Console.WriteLine("{0}; RL started: {1}; ThreadId: {2};", DateTime.Now.Ticks, tableName, transactionThreadId);
            
            TRLA trla=null;


            _sync_tablesReadLocks.EnterUpgradeableReadLock();
            try
            {
                //Addin Thread as key and List<tableNames> as Value, to search quickly by thread
                //We have option either iterate via all tables, and search there transactions id, or to iterate threads and search there tables. Iterate threads will be faster.
                //When transaction will be finished.
                if (!threadReadTables.ContainsKey(transactionThreadId))
                {
                    _sync_tablesReadLocks.EnterWriteLock();
                    try
                    {
                        List<string> tables = new List<string>();
                        tables.Add(tableName);
                        threadReadTables.Add(transactionThreadId, tables);
                    }
                    catch (System.Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        _sync_tablesReadLocks.ExitWriteLock();
                    }
                   
                }
                else
                {
                    if (!threadReadTables[transactionThreadId].Contains(tableName))
                    {
                        threadReadTables[transactionThreadId].Add(tableName);
                    }
                }

                //Adding threads to tables, to answer to the compactor on the question IfTableHasOpenReadLocatorsTill 
                //(we need to to know open locator dateTime, to gracefully delete table from VFS, when this old locator finished its job)
                if (!tablesReadLocks.ContainsKey(tableName))
                {
                    _sync_tablesReadLocks.EnterWriteLock();
                    try
                    { 
                        trla = new TRLA();
                        trla.Add(transactionThreadId);
                        tablesReadLocks.Add(tableName, trla);

                    }
                    catch (System.Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        _sync_tablesReadLocks.ExitWriteLock();
                    }
                }
                else
                {
                    tablesReadLocks[tableName].Add(transactionThreadId);
                }
            }
            catch (System.Exception ex)
            {
                throw ex;              
            }
            finally
            {
                _sync_tablesReadLocks.ExitUpgradeableReadLock();
            }



        }

       
        /// <summary>
        /// Called by TransactionCoordinator. Unregister transaction
        /// </summary>
        /// <param name="transactionThreadId"></param>
        public void TransactionIsFinished(int transactionThreadId)
        {
            //Unregister all transaction's READ Locators
            IReadLocator irl=null;

            _sync_tablesReadLocks.EnterWriteLock();
            try
            {
                if (threadReadTables.ContainsKey(transactionThreadId))
                {
                    foreach (var table in threadReadTables[transactionThreadId])
                    {
                        tablesReadLocks[table].Remove(transactionThreadId);

                        //Removing table from the list if it doesn't contain any more threads
                        if (!tablesReadLocks[table].ContainsThreads())
                            tablesReadLocks.Remove(table);

                        //Removing reserved read locators for the specific table from TableLocator (IReadLocator)
                        irl = this._transactionCoordinator.TablesCoordinator.GetTableAsIReadLocator(table);
                        if (irl != null)
                            irl.RemoveReadLocatorsForTheThread(transactionThreadId);
                    }

                    threadReadTables.Remove(transactionThreadId);
                }

            }
            catch (System.Exception ex)
            {
                throw ex;
            }
            finally
            {
                _sync_tablesReadLocks.ExitWriteLock();
            }
            
        }


        /// <summary>
        /// ReadLocatorsHandler to the system answers.
        /// We want to find out if we have open Read Locators till concrete dateTime for the specified table
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="dt"></param>
        /// <returns></returns>
        public bool IfTableHasOpenReadLocatorsTill(string tableName, DateTime dt)
        {

            _sync_tablesReadLocks.EnterReadLock();
            try
            {
                if (!tablesReadLocks.ContainsKey(tableName))
                    return false;

                return tablesReadLocks[tableName].IfThereAreOpenReadLocatorsTillSpecifiedDateTime(dt);
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
            finally
            {
                _sync_tablesReadLocks.ExitReadLock();
            }

            
        }

    }
}
